// Copyright DustedPixels.com 2008. All rights reserved.

package com.dustedpixels.asm;

import java.util.HashSet;
import java.util.Set;

import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;

import com.dustedpixels.asm.Access;
import com.dustedpixels.asm.Types;

/**
 * @author micapolos@gmail.com (Michal Pociecha-Los)
 */
public final class MethodNodes {
  /**
   * Finds method node in given class node.
   *  
   * @param classNode Class node to look for a method.
   * @param name Method name.
   * @param desc Method descriptor.
   * @return Found method node or null.
   */
  public static final MethodNode find(ClassNode classNode, String name, String desc) {
    for (Object object : classNode.methods) {
      MethodNode methodNode = (MethodNode) object;
      if (methodNode.name.equals(name) && methodNode.desc.equals(desc)) {
        return methodNode;
      }
    }
    return null;
  }
  
  /**
   * Returns true if given method is a constructor.
   * 
   * @param method a method to check
   * @return true, if method is a constructor
   */
  public static final boolean isConstructor(MethodNode method) {
    return method.name.equals("<init>");
  }
  
  public static final MethodNode shallowCopy(MethodNode method) {
    return new MethodNode(
        method.access, 
        method.name,
        method.desc,
        method.signature,
        (String[]) method.exceptions.toArray(new String[0]));   
  }
  
  public static final MethodNode copy(MethodNode method) {
    MethodNode copiedMethod = shallowCopy(method);   
    accept(method, copiedMethod);
    return copiedMethod;
  }
  
  public static void accept(MethodNode method, MethodVisitor visitor) {
    method.accept(new LabelFixingMethodVisitor(visitor));
  }
  
  public static MethodNode removeUnusedLabels(MethodNode method) {
    MethodNode copiedMethod = shallowCopy(method);   

    Set<Label> usedLabels = new HashSet<Label>();
    method.accept(new UsedLabelsCollector(usedLabels));
    method.accept(new UnusedLabelsRemover(copiedMethod, usedLabels));
    
    return copiedMethod;
  }
  
  public static Set<Integer> findWrittenLocals(MethodNode method) {
    Set<Integer> writtenLocals = new HashSet<Integer>();
    MethodVisitor collector = new WriteLocalsCollector(writtenLocals);
    method.accept(collector);
    return writtenLocals;
  }
  
  /**
   * Returns array of argument types of given method, including implicit "this"
   * argument for non-static methods.
   * 
   * @param method method
   * @return sizes of locals reserved for method arguments.
   */
  public static Type[] getAllArgTypes(MethodNode method) {
    Type[] argTypes = Type.getArgumentTypes(method.desc);
    
    if (!Access.isStatic(method.access)) {
      Type[] types = new Type[argTypes.length + 1];
      types[0] = Types.ofObject();
      System.arraycopy(argTypes, 0, types, 1, argTypes.length);
      return types;
    } else {
      return argTypes;
    }
  }
  
  /**
   * Allocates local indices for given array of types.
   * 
   * @param types array of types
   * @param startLocal index of the first local to allocate
   * @return an array with allocated local indices
   */
  public static int[] allocateLocals(Type[] types, int startLocal) {
    int[] locals = new int[types.length + 1];
    int local = startLocal;
    for (int i = 0; i < types.length; i++) {
      Type type = types[i];
      locals[i] = local;
      local += type.getSize();
    }
    locals[types.length] = local;
    return locals;
  }
}
